﻿using UnityEngine;

namespace CheatMenu.Utils
{
    public static class GUIUtil
    {
        private static Texture2D backgroundTexture;

        public static Color BackgroundTintColor { get; set; } = new Color(1f, 1f, 1f, 1f);

        private static Color backgroundColor = new Color(0.6f, 0.6f, 0.6f, 1f);
        public static Color BackgroundColor
        {
            get => backgroundColor;
            set
            {
                backgroundColor = value;
                backgroundTexture = null;
            }
        }

        public static void DrawRoundedRectangle(Rect windowRect, float radius = 10f)
        {
            if (backgroundTexture == null)
                backgroundTexture = CreateColorTexture(backgroundColor);

            GUI.DrawTexture(windowRect, backgroundTexture, ScaleMode.StretchToFill, false, 0f, BackgroundTintColor, 0f, radius);
        }

        private static Texture2D CreateColorTexture(Color color)
        {
            Texture2D result = new Texture2D(1, 1, TextureFormat.RGBA32, false);
            result.SetPixel(0, 0, color);
            result.Apply();

            return result;
        }

        public static bool MouseScrollingAndContained(Rect windowRect)
        {
            var mouseScreenSpace = Input.mousePosition;
            mouseScreenSpace.y = Screen.height - mouseScreenSpace.y;
            return Input.GetAxis("Mouse ScrollWheel") != 0 && windowRect.Contains(mouseScreenSpace);
        }

        //AlignRect takes a width and a height returned a rect snapped to the edge of the screen as defined by the alignment
        //If you want a different rect than the screen, use the parameter parentRect
        //You can adjust the result position with xOffset and yOffset
        public static Rect AlignRect(float width, float height, Alignment alignment)
        => AlignRect(width, height, new Rect(0, 0, Screen.width, Screen.height), alignment, 0f, 0f);
        public static Rect AlignRect(float width, float height, Alignment alignment, float xOffset, float yOffset)
        => AlignRect(width, height, new Rect(0, 0, Screen.width, Screen.height), alignment, xOffset, yOffset);
        public static Rect AlignRect(float width, float height, Rect parentRect, Alignment alignment)
        => AlignRect(width, height, parentRect, alignment, 0f, 0f);

        public static Rect AlignRect(float width, float height, Rect parentRect, Alignment alignment, float xOffset, float yOffset)
        {
            Rect oRect;

            switch (alignment)
            {
                case Alignment.TopLeft:
                    oRect = new Rect(0f, 0f, width, height);
                    break;
                case Alignment.TopRight:
                    oRect = new Rect(parentRect.width - width, 0f, width, height);
                    break;
                case Alignment.TopCentre:
                    oRect = new Rect(parentRect.width * 0.5F - width * 0.5F, 0f, width, height);
                    break;
                case Alignment.Centre:
                    oRect = new Rect(parentRect.width * 0.5F - width * 0.5F, parentRect.height * 0.5F - height * 0.5F, width, height);
                    break;
                case Alignment.Right:
                    oRect = new Rect(parentRect.width - width, parentRect.height * 0.5F - height * 0.5F, width, height);
                    break;
                case Alignment.BottomRight:
                    oRect = new Rect(parentRect.width - width, parentRect.height - height, width, height);
                    break;
                case Alignment.BottomCentre:
                    oRect = new Rect(parentRect.width * 0.5F - width * 0.5F, parentRect.height - height, width, height);
                    break;
                case Alignment.BottomLeft:
                    oRect = new Rect(0f, parentRect.y + parentRect.height - height, width, height);
                    break;
                case Alignment.Left:
                    oRect = new Rect(0f, parentRect.height * 0.5F - height * 0.5F, width, height);
                    break;
                case Alignment.None:
                default:
                    oRect = new Rect(0f, 0f, width, height);
                    break;
            }

            oRect.x += parentRect.x + xOffset;
            oRect.y += parentRect.y + yOffset;
            return oRect;
        }

        // Rect extensions
        // Reduce the size of the rect from the center
        public static Rect Shrink(this Rect r, float nbPixels) => r.Shrink(nbPixels, nbPixels);
        public static Rect Shrink(this Rect r, float nbPixelX, float nbPixelY)
        {
            return new Rect(r.x + nbPixelX, r.y + nbPixelY, r.width - nbPixelX * 2f, r.height - nbPixelY * 2f);
        }

        // Enhance the size of the rect from the center
        public static Rect Grow(this Rect r, float nbPixels) => r.Shrink(-nbPixels, -nbPixels);
        public static Rect Grow(this Rect r, float nbPixelX, float nbPixelY) { return r.Shrink(-nbPixelX, -nbPixelY); }

        // Make sure the rect is contained inside another rect. It's size isn't changed.
        public static Rect ClampPosition(this Rect r, Rect borderRect)
        {
            return new Rect(Mathf.Clamp(r.x, borderRect.x, borderRect.x + borderRect.width - r.width),
                            Mathf.Clamp(r.y, borderRect.y, borderRect.y + borderRect.height - r.height),
                            r.width, r.height);
        }

        // Translate the rect
        public static Rect MoveX(this Rect r, float xMovement) => r.Move(xMovement, 0f);
        public static Rect MoveY(this Rect r, float yMovement) => r.Move(0f, yMovement);
        public static Rect Move(this Rect r, Vector2 movement) => r.Move(movement.x, movement.y);
        public static Rect Move(this Rect r, float xMovement, float yMovement)
        {
            return new Rect(r.x + xMovement, r.y + yMovement, r.width, r.height);
        }

        // Return the rect relatively to another one, like a groupe would do.
        public static Rect RelativeTo(this Rect r, Rect to)
        {
            return new Rect(r.x - to.x, r.y - to.y, r.width, r.height);
        }

        // Return the rect as one level of group higher.
        public static Rect InverseTransform(this Rect r, Rect from)
        {
            return new Rect(r.x + from.x, r.y + from.y, r.width, r.height);
        }

        // Quick access to the screen rect
        public static Rect ScreenRect { get { return new Rect(0f, 0f, Screen.width, Screen.height); } }

        // MOUSE FUNCTIONS
        // Easy access to the gui mouse pos
        public static Vector2 MousePos { get { return Event.current.mousePosition; } }
        // gui mouse pos with the y inverted
        public static Vector2 MousePosInvertY { get => FlipY(MousePos); }
        public static Vector2 FlipY(Vector2 inPos) { inPos.y = Screen.height - inPos.y; return inPos; }
        public static Vector2 MouseRelativePos(Rect rect) => RelativePos(rect, MousePos.x, MousePos.y);
        // Give the inPos relative to the input rect
        public static Vector2 RelativePos(Rect rect, Vector2 inPos) => RelativePos(rect, inPos.x, inPos.y);
        public static Vector2 RelativePos(Rect rect, Vector3 inPos) => RelativePos(rect, inPos.x, inPos.y);
        public static Vector2 RelativePos(Rect rect, float x, float y) => new Vector2(x - rect.x, y - rect.y);
        // Give the inPos as one group higher
        public static Vector2 InverseTransformPoint(Rect rect, Vector3 inPos)
        {
            return new Vector2(rect.x + inPos.x, rect.y + inPos.y);
        }

        // GUI Event extensions
        // Checks for Key Down / Up event of a certain key
        public static bool GetKeyDown(this Event aEvent, KeyCode aKey)
        {
            return (aEvent.type == EventType.KeyDown && aEvent.keyCode == aKey);
        }
        public static bool GetKeyUp(this Event aEvent, KeyCode aKey)
        {
            return (aEvent.type == EventType.KeyUp && aEvent.keyCode == aKey);
        }

        // Checks for mouse Down / Up event of a certain mouse button
        public static bool GetMouseDown(this Event aEvent, int aButton)
        {
            return (aEvent.type == EventType.MouseDown && aEvent.button == aButton);
        }
        public static bool GetMouseUp(this Event aEvent, int aButton)
        {
            return (aEvent.type == EventType.MouseUp && aEvent.button == aButton);
        }

        // Checks for mouse Down / Up event of a certain mouse button inside the given Rect
        public static bool GetMouseDown(this Event aEvent, int aButton, Rect aRect)
        {
            return (aEvent.type == EventType.MouseDown && aEvent.button == aButton && aRect.Contains(aEvent.mousePosition));
        }
        public static bool GetMouseUp(this Event aEvent, int aButton, Rect aRect)
        {
            return (aEvent.type == EventType.MouseUp && aEvent.button == aButton && aRect.Contains(aEvent.mousePosition));
        }
    }
}
